package com.daffodil.storage.service.impl;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.apache.commons.io.filefilter.PrefixFileFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;

import com.daffodil.storage.exception.BaseException;
import com.daffodil.storage.model.MediaMimetype;
import com.daffodil.storage.model.Page;
import com.daffodil.storage.model.StorageResult;
import com.daffodil.storage.properties.DiskProperties;
import com.daffodil.storage.properties.StorageProperties;
import com.daffodil.storage.service.AStorageService;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;

/**
 * 本地磁盘存储
 * @author yweijian
 * @date 2022年12月8日
 * @version 2.0.0
 * @description
 */
@Slf4j
@Service
@Configuration
@ConditionalOnProperty(name = StorageProperties.NAME, havingValue = StorageProperties.DISK)
public class StorageDiskServiceImpl extends AStorageService {

    @Autowired
    private StorageProperties storageProperties;

    @Override
    public StorageResult upolad(StorageResult result, InputStream inputStream) {
        log.debug("本地磁盘对象存储 > 上传对象 > fileName={}", result.getOrigin());

        DiskProperties properties = storageProperties.getDisk();
        result.setUrl(properties.getDomain() + StrUtil.SLASH + result.getKey());

        //校验磁盘的读写权限
        String filePath = properties.getBucketName() + File.separator + result.getKey().replace(StrUtil.SLASH, File.separator);
        File file = new File(filePath);
        File parentFile = file.getParentFile();
        if((!parentFile.exists() && !parentFile.mkdirs()) || !parentFile.canWrite()) {
            throw new BaseException(18003);
        }

        try {
            FileUtils.copyInputStreamToFile(inputStream, file);
            log.debug("本地磁盘对象存储 > 上传对象 > file={}", filePath);
        } catch (IllegalStateException | IOException e) {
            log.error(e.getMessage(), e);
            throw new BaseException(e.getMessage());
        }

        log.debug("本地磁盘对象存储 > 上传对象 > result={}", result.toString());
        return result;
    }

    /**
     * -本地磁盘对象分页查询，超大目录数据会内存溢出异常。
     * @example { folder: dev-upload/ name: abc }
     * @remark 分页page.totalRow 总记录数只记录有效数据文件对象，不记录对象目录
     */
    @Override
    public List<StorageResult> list(String folder, String name, Page page) {
        List<StorageResult> results = new ArrayList<StorageResult>();
        DiskProperties properties = storageProperties.getDisk();
        StringBuffer prefix = new StringBuffer();
        prefix.append(properties.getBucketName());
        prefix.append(StrUtil.SLASH);
        if(ObjectUtil.isNotEmpty(folder)) {
            //目录不合法
            if(folder.contains(StrUtil.DOT)) {
                return results;
            }
            folder = folder.startsWith(StrUtil.SLASH) ? folder.substring(1) : folder;
            prefix.append(folder);
        }

        File directory = new File(prefix.toString().replace(File.separator, StrUtil.SLASH));
        if(!directory.exists()) {
            return results;
        }

        //目录数据
        String[] directorys = directory.list();
        if(ObjectUtil.isNotEmpty(directorys)) {
            for (int i = 0; i < directorys.length; i++) {
                File file = new File(directory.getPath(), directorys[i]);
                if(file.isDirectory()) {
                    String key = file.getPath().replace(properties.getBucketName(), StrUtil.EMPTY).replace(File.separator, StrUtil.SLASH).substring(1) + StrUtil.SLASH;
                    StorageResult result = StorageResult.builder().name(FileNameUtil.getName(key)).origin(FileNameUtil.getName(key))
                            .prefix(FileNameUtil.getPrefix(key)).suffix(FileNameUtil.getSuffix(key)).size(0L).mime(StrUtil.EMPTY).key(key)
                            .url(properties.getDomain() + StrUtil.SLASH + key).metadata(MapUtil.empty()).build();
                    results.add(result);
                }
            }
        }

        //文件对象数据
        IOFileFilter fileFilter = new PrefixFileFilter(ObjectUtil.isEmpty(name) ? StrUtil.EMPTY : name);
        IOFileFilter dirFilter = new PrefixFileFilter(directory.getPath());
        Collection<File> list = FileUtils.listFiles(directory, fileFilter, dirFilter);
        if(list == null || page.getFromIndex() > list.size()) {
            return results;
        }

        page.setTotalRow(list.size());
        int fromIndex = page.getFromIndex();
        int toIndex = Math.min(page.getToIndex(), list.size());
        if(fromIndex > toIndex) {
            return results;
        }

        File[] files = Arrays.copyOfRange(list.toArray(new File[list.size()]), fromIndex, toIndex);
        for(int i = 0; i < files.length; i++) {
            File file = files[i];
            String key = file.getPath().replace(properties.getBucketName(), StrUtil.EMPTY).replace(File.separator, StrUtil.SLASH).substring(1);
            StorageResult result = StorageResult.builder().name(FileNameUtil.getName(key)).origin(FileNameUtil.getName(key))
                    .prefix(FileNameUtil.getPrefix(key)).suffix(FileNameUtil.getSuffix(key)).size(file.length())
                    .mime(MediaMimetype.getInstance().getMimetype(FileNameUtil.getSuffix(key))).key(key)
                    .url(properties.getDomain() + StrUtil.SLASH + key).metadata(MapUtil.empty()).build();
            results.add(result);
        }

        return results;
    }

    @Override
    public void download(String key, HttpServletResponse response) {
        String fileName = FileNameUtil.getName(key);
        if(ObjectUtil.isEmpty(fileName)) {
            return;
        }
        DiskProperties properties = storageProperties.getDisk();
        String filePath = properties.getBucketName() + File.separator + key.replace(StrUtil.SLASH, File.separator);
        File file = new File(filePath);
        if(!file.exists() || !file.canRead()) {
            throw new BaseException(18003);
        }
        try {
            response.setCharacterEncoding(StandardCharsets.UTF_8.name());
            response.setContentType(MediaType.MULTIPART_FORM_DATA_VALUE);
            response.setHeader(HttpHeaders.CONTENT_DISPOSITION, ContentDisposition.attachment().filename(fileName, StandardCharsets.UTF_8).build().toString());
            FileUtil.writeToStream(file, response.getOutputStream());
        } catch (IORuntimeException | IOException e) {
            log.error(e.getMessage(), e);
            throw new BaseException(e.getMessage());
        }
    }

    @Override
    public void remove(String key) {
        DiskProperties properties = storageProperties.getDisk();
        String filePath = properties.getBucketName() + File.separator + key.replace(StrUtil.SLASH, File.separator);
        File file = new File(filePath);
        if(file != null && file.exists()) {
            if(file.canExecute()) {
                file.delete();
            }else {
                throw new BaseException(18003);
            }
        }
    }

}
